package com.norbitltd.guavaleaves.reflection;

import java.lang.reflect.Method;
import java.util.List;

import org.apache.commons.lang3.StringUtils;

import com.google.common.base.Function;
import com.google.common.collect.Lists;
import com.norbitltd.guavaleaves.java.lang.ObjectFunctions;

public class ReflectionFunctions {

	public static <T extends Object, V extends Object> Function<T, V> getProperty(
			final String propertyName) {
		final String methodName = "get" + StringUtils.capitalize(propertyName);
		return executeMethod(methodName);
	}

	public static <T extends Object, V extends Object> Function<T, V> executeMethod(
			final String methodName, final Object... args) {
		return new Function<T, V>() {
			@SuppressWarnings("unchecked")
			public V apply(T input) {
				Class<?>[] argTypes = null;
				try {					
					if (args == null || args.length == 0) {
						Method method = input.getClass().getMethod(methodName);
						return (V) method.invoke(input);
					} else {
						List<Object> argumentsList = Lists.newArrayList(args); 
						List<Class<?>> argumentTypesList = Lists.transform(
								argumentsList, ObjectFunctions.getClassObject());
						argTypes = argumentTypesList.toArray(new Class<?>[1]);
						Method method = input.getClass().getMethod(methodName, argTypes);
						return (V) method.invoke(input, args);
					}
				} catch (Exception e) {
					throw new RuntimeException("Unable to get or execute "
							+ methodName + " method with argumentTypes " + argTypes + " for an object!");
				}
			}
		};
	}

	public static <T extends Object, V extends Object> Function<T, V> executeMethod(
			final String methodName, final Class<?>[] argTypes,
			final Object... args) {
		if (argTypes != null && args != null && args.length != argTypes.length) {
			throw new RuntimeException(
					"List of argument types and arguments have to have the same size!");
		} else {
			return new Function<T, V>() {
				@SuppressWarnings("unchecked")
				public V apply(T input) {
					try {
						if (args == null || args.length == 0) {
							Method method = input.getClass().getMethod(
									methodName);
							return (V) method.invoke(input);
						} else {
							Method method = input.getClass().getMethod(
									methodName, argTypes);
							return (V) method.invoke(input, args);
						}
					} catch (Exception e) {
						throw new RuntimeException("Unable to get or execute "
								+ methodName + " method for an object!");
					}
				}
			};
		}
	}
}
